package com.isyscore.os.driver;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.util.AttributeKey;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.net.InetSocketAddress;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author stw
 */
@ChannelHandler.Sharable
public class TcpHandler extends SimpleChannelInboundHandler<ByteBuf> {
    private final static Logger log = LoggerFactory.getLogger(TcpHandler.class);
    private Channel serverChannel;

    /**
     * key:<ip:port>
     */
    private Map<String, Channel> addrChannelMap = new ConcurrentHashMap<>();
    /**
     * key:<ip>
     */
    private Map<String, Channel> ipChannelMap = new ConcurrentHashMap<>();

    private final static AttributeKey<byte[]> lastRecvMsg = AttributeKey.valueOf("ISYS_LAST_RECV_MSG");

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        InetSocketAddress address = (InetSocketAddress) ctx.channel().remoteAddress();
        String addString = address.getHostString() + ":" + address.getPort();
        addrChannelMap.put(addString, ctx.channel());
        ipChannelMap.put(address.getHostString(), ctx.channel());
        log.info(addString + "建立连接");
    }

    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        InetSocketAddress address = (InetSocketAddress) ctx.channel().remoteAddress();
        String addString = address.getHostString() + ":" + address.getPort();
        addrChannelMap.remove(addString);
        ipChannelMap.remove(address.getHostString());
        log.info(addString + "断开连接");
    }

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, ByteBuf byteBuf) throws Exception {
        byte[] bytes = new byte[byteBuf.readableBytes()];
        byteBuf.readBytes(bytes);
        ctx.channel().attr(lastRecvMsg).set(bytes);
    }

    public byte[] sendAndRecv(String addr, byte[] bytes, long timeWait) {
        Channel channel = addrChannelMap.get(addr);
        if (channel == null){
            channel = ipChannelMap.get(addr);
        }
        if (channel == null) {
            log.warn("未获取到channel");
            return null;
        }
        channel.attr(lastRecvMsg).set(null);
        long start = System.currentTimeMillis();
        ByteBuf byteBuf = channel.alloc().heapBuffer(bytes.length);
        byteBuf.writeBytes(bytes);
        channel.writeAndFlush(byteBuf);
        while (channel.attr(lastRecvMsg).get() == null) {
            long end = System.currentTimeMillis();
            if ((end - start) > timeWait) {
                return null;
            }
            try {
                Thread.sleep(20);
            } catch (InterruptedException e) {
                log.info(e.getMessage());
            }
        }
        return channel.attr(lastRecvMsg).get();
    }

    public int startServer(TcpHandler tcpHandler, int port) {
        new Thread(() -> {
            EventLoopGroup bossGroup = new NioEventLoopGroup(1);
            EventLoopGroup workerGroup = new NioEventLoopGroup();
            try {
                ServerBootstrap b = new ServerBootstrap();
                b.group(bossGroup, workerGroup)
                        .channel(NioServerSocketChannel.class)
                        .option(ChannelOption.SO_BACKLOG, 128)
                        .childOption(ChannelOption.SO_KEEPALIVE, true)
                        .childHandler(new ChannelInitializer<SocketChannel >() {
                            @Override
                            public void initChannel(SocketChannel  ch) throws Exception {
                                ch.pipeline().addLast(tcpHandler);
                            }
                        });

                // Start the client.
                ChannelFuture f = b.bind(port).sync();
                // Wait until the connection is closed.
                serverChannel = f.channel();
                serverChannel.closeFuture().sync();
            } catch (InterruptedException e) {
                log.warn("Tcp接收modbus线程被打断");
            } finally {
                workerGroup.shutdownGracefully();
            }
        }).start();
        return 0;
    }

    public void close() {
        log.warn("手动触发close");
        serverChannel.close();
    }
}
